package com.ouroboroswiki.web;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.ouroboroswiki.core.Content;
import com.ouroboroswiki.core.ContentException;
import com.ouroboroswiki.core.ContentParameters;
import com.ouroboroswiki.core.ContentPath;
import com.ouroboroswiki.core.ContentUtil;

public class OuroborosServlet extends HttpServlet {

	private static final Logger log = Logger.getLogger(OuroborosServlet.class.getName());
	
	private static final long serialVersionUID = 0;
	
	public static final String DEFAULT_ACTION_PARAMETER = "action";
	public static final String DEFAULT_CONTENT_SOURCE = "default";
	
	public static final String DEFAULT_PIPE = "default-pipe";
	
	public static final String SITE_BASE_KEY = "site-base";
	public static final String WIKI_BASE_KEY = "wiki-base";
	
	public static final String WIKI_PREFIX = "wiki-prefix";
	
	public static final String PRINCIPAL = "principal";
	
	private String actionParameter;
	
	private String[] defaultPipe;
	
	private Map<String, OuroborosServletAction> servletActions;
	private OuroborosServletAction defaultServletAction;

	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String actionName = request.getParameter(actionParameter);
		OuroborosServletAction action = servletActions.get(actionName);
		if( action == null ) {
			action = defaultServletAction;
		}
		String pathInfo = request.getPathInfo();
		if( pathInfo == null ) {
			pathInfo = "";
		}
		if( pathInfo.endsWith("/") ) {
			// make it consistent
			pathInfo = pathInfo.substring(0, pathInfo.length()-1);
		}
		if( pathInfo.startsWith("/") ) {
			pathInfo = pathInfo.substring( 1 );
		}
		
		ContentPath path;
		int pipeIndex = pathInfo.indexOf('.');
		if( pipeIndex < 0 ) {
			String name = pathInfo;
			path = new ContentPath(name);
			for( int i=0; i<defaultPipe.length; i++ ) {
				path = new ContentPath( defaultPipe[i], path, i==defaultPipe.length-1 );
			}
		} else {
			path = ContentUtil.singleFromString(pathInfo);
		}
		
		Object principal = request.getSession().getAttribute(PRINCIPAL);
		Object[] principalPointer = new Object[]{principal};
		
		@SuppressWarnings("rawtypes")
		Map parameters = request.getParameterMap();
		ContentSource contentSource;
		Object parameter;
		try {
			ContentOperation operation = action.getOperation();
			if( operation != null ) {
				String operationResult = operation.perform(principalPointer, path, parameters);
				if( principalPointer[0] != principal ) {
					principal = principalPointer[0];
					request.getSession().setAttribute(PRINCIPAL, principal);
				}
				parameter = operationResult;
				contentSource = action.getContentSource(operationResult);
			} else {
				parameter = null;
				contentSource = action.getContentSource(DEFAULT_CONTENT_SOURCE);
			}
		} catch( Exception ex ) {
			log.log(Level.SEVERE, "error handling content for request "+request.getRequestURI(), ex);
			parameter = ex;
			contentSource = action.getErrorContentSource();
		}
		// TODO handle the error more elegantly (default error/content pages)
		if( contentSource != null ) {
			try {
				// TODO using all parameters is inefficient for caching
				@SuppressWarnings("unchecked")
				HashMap<String, Object> properties = new HashMap<String, Object>(parameters);
				// TODO add errors (anything else?)
				properties.put(ContentParameters.PROPERTY_ORIGINAL_PATH, ContentUtil.toString(path.getNextContext()));
				properties.put(ContentParameters.PROPERTY_ORIGINAL_PIPE, ContentUtil.toString(path));
				properties.put(ContentParameters.PROPERTY_REQUEST_URI, request.getRequestURL().toString());
				String baseURI = getBaseURI( request );
				properties.put(ContentParameters.PROPERTY_BASE_URI, baseURI );

				Content content = contentSource.getContent(principal, parameter, path, properties);
				String contentType = content.getMimeType();
				if( contentType != null ) {
					response.setContentType(contentType);
				}
				OutputStream outs = response.getOutputStream();
				content.write(outs);
			} catch( ContentException ex ) {
				log.log(Level.SEVERE, "error obtaining content for request", ex);
			}
		}
	}
	
	private String getBaseURI( HttpServletRequest request ) {
		return request.getScheme() +"://" + request.getServerName() + ":"+request.getServerPort() + request.getContextPath() + "/" + request.getServletPath();
	}

	@Override
	@SuppressWarnings("unchecked")
	public void init(ServletConfig config) throws ServletException {
		String contextPath = config.getServletContext().getContextPath();
		String wikiPath = contextPath;
		String wikiPrefix = config.getInitParameter(WIKI_PREFIX); 
		String defaultPipeString = config.getInitParameter(DEFAULT_PIPE);
		if( defaultPipeString != null ) {
			this.defaultPipe = defaultPipeString.split("/");
		} else {
			this.defaultPipe = new String[0];
		}
		if( wikiPrefix != null ) {
			wikiPath += wikiPrefix;
		}
		super.init(config);
		
		Properties properties = new Properties();
		properties.put(SITE_BASE_KEY, contextPath);
		properties.put(WIKI_BASE_KEY, wikiPath);
		
		String internalPropertiesString = config.getInitParameter("internal_properties");
		if( internalPropertiesString != null ) {
			String[] internalPropertiesPaths = internalPropertiesString.split(",");
			for( String internalPropertiesPath : internalPropertiesPaths ) {
				InputStream ins = null;
				try {
					ins = this.getClass().getResourceAsStream(internalPropertiesPath);
					properties.load(ins);
				} catch( Exception ex ) {
					log.log( Level.SEVERE, "unable to open "+internalPropertiesPath, ex );
				} finally {
					if( ins != null ) {
						try {
							ins.close();
						} catch( Exception ex ) {
							log.log( Level.WARNING, "unable to close "+internalPropertiesPath, ex );
						}
					}
				}
			}
		}
		String externalPropertiesString = config.getInitParameter("external_properties");
		if( externalPropertiesString != null ) {
			String[] externalPropertiesPaths = externalPropertiesString.split(",");
			for( String externalPropertiesPath : externalPropertiesPaths ) {
				InputStream ins = null;
				try {
					ins = new FileInputStream( externalPropertiesPath );
					properties.load(ins);
				} catch( Exception ex ) {
					log.log( Level.SEVERE, "unable to open "+externalPropertiesPath );
				} finally {
					if( ins != null ) {
						try {
							ins.close();
						} catch( Exception ex ) {
							log.log( Level.WARNING, "unable to close "+externalPropertiesPath, ex );
						}
					}
				}
			}
		}

		Enumeration<String> initParameterNames = config.getInitParameterNames();
		while( initParameterNames.hasMoreElements() ) {
			String initParameter = initParameterNames.nextElement();
			properties.put( initParameter, config.getInitParameter(initParameter) );
		}
		
		
		String beansPath = config.getInitParameter("beans");
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
		PropertyPlaceholderConfigurer propertyConfigurer = new PropertyPlaceholderConfigurer();
		propertyConfigurer.setProperties(properties);
		context.addBeanFactoryPostProcessor(propertyConfigurer);
		context.setConfigLocation(beansPath);
		context.refresh();
		
		this.servletActions = (Map<String, OuroborosServletAction>)context.getBean("actions");
		this.defaultServletAction = (OuroborosServletAction)context.getBean("default-action");
		
		// load up the operations
		actionParameter = DEFAULT_ACTION_PARAMETER;
	}
	
	
}
